Function Check-AdminRight(){ # this function is used for checking if the current user has admin access
	$cwi=[Security.Principal.WindowsIdentity]::GetCurrent()
	$cwp=[Security.Principal.WindowsPrincipal]$cwi
	if(!$cwp.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)){
		return $false
	}else{return $true}
}
Function Check-FIDLogStatusToVDI( ){ # this function is used for checking if the given fid has loged in the given vdi by HKCU contains FID's sid
    param(
    	[Parameter(Mandatory=$True)]
    	[string]$vdi,
    	[Parameter(Mandatory=$True)]
    	[string]$fid,
    	[Parameter(Mandatory=$false)]
    	[string]$domain="nam"
    )
	if($fid -notlike "*\*"){$fid="$domain\$fid"}
	$sid = (New-Object System.Security.Principal.NTAccount($fid)).Translate([System.Security.Principal.SecurityIdentifier]).value
	$reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey('Users',$vdi) 
	If( $reg.OpenSubKey($sid) -eq $null ){return $false }else{return $true }
}
Function Create-Object($pro_file){ # read .propertuies file and convert to object
	$obj=New-Object object
	$pro_list=(get-content $pro_file)|?{!$_.startswith('#')}
	try{
		foreach($line in $pro_list){
			$line=$line.split("=")
			Add-Member -InputObject $obj -Name $line[0].trim() -Value $line[1].trim() -MemberType NoteProperty
		}
		return $obj
	}catch{
		return $null
	}finally{}
}
Function Get-XMLValue(){ # read XML file and get the node value
	param(
		[Parameter(Mandatory=$True)]
		[string]$xpath,
		[Parameter(Mandatory=$True)]
		[object]$xmlFile
	)
	try{
		If( $xmlFile.gettype().name -eq "String" ){$xmlFile = [XML](get-content (Get-Item -Path $xmlFile).FullName)}
		$text = $xmlFile.SelectSingleNode($xpath).InnerText
		If([string]::IsNullOrWhiteSpace($text)){return $null}else{return $text}
	}catch{return $null	}
}
Function Start-BulkThreads(){	# -Action { Stop-Process -Id $PID }
	# useing parallel threads to run scripts in the same time so that it will speedup the running duration
	#reference https://www.codeproject.com/tips/895840/multi-threaded-powershell-cookbook
    param(
    	[Parameter(Mandatory=$True)]
    	[Object[]]$vdilist,             # the vdi list that scripts to be ran
    	[Parameter(Mandatory=$True)]    
    	[ScriptBlock]$Action,           # the actual action that each thread will run
    	[Parameter(Mandatory=$True)]    
    	[AllowNull()]
    	[Object[]]$ActionParams,        # the parameters that each thread needs, this can be a single or array of parameter
    	[Parameter(Mandatory=$false)]   
    	[int]$maxCnt=20                 # the default amount of powershell threads, if this number is big, then the system might get stuck
    )
	$results=@() # this is used to collect the result from each thread
	$vdiCounts = $vdilist.Count
	If( $vdiCounts -eq 1 ){ # no need to create thread pool to run it
		If( [string]::IsNullOrWhiteSpace($vdilist[0]) ){
		    write-error "vdilist only contains 1 line but it's null"
		}else{& $Action $vdilist[0] 1 $ActionParams}
	}else{
		$vdiCounts = $vdilist.Count ; $throttleLimit=$maxCnt ; If($vdiCounts -le $maxCnt ){$throttleLimit=$vdiCounts}
		# creaate the thread pool and run each thread based the vdi list amount
		$SessionState = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
		$Pool = [runspacefactory]::CreateRunspacePool(1, $throttleLimit, $SessionState, $Host)
		$Pool.Open() ; $threads = @() ; $cnt=1
		$handles = foreach($line in $vdilist){
			if( [string]::IsNullOrWhiteSpace($line ) ){continue}
			$powershell = [powershell]::Create().AddScript($Action).AddArgument($line).AddArgument($cnt).AddArgument($ActionParams)
			$powershell.RunspacePool = $Pool ;	$powershell.BeginInvoke() ; $threads += $powershell ; $cnt++
		}
		# wait each thread to be done and then collecte its return value
		do{
		  $i = 0 ;  $done = $true
		  foreach ($handle in $handles) {
			if($handle -ne $null){if($handle.IsCompleted){$results+=$threads[$i].EndInvoke($handle);$threads[$i].Dispose();$handles[$i]=$null}else{$done = $false}}
			$i++
		  }
		  if (-not $done) { Start-Sleep -Milliseconds 200 }
		} until ($done)
	}
	return $results # return the value collection from each thread and then export to csv or take other further actions
}
Function Check-FIDExistence(){ # this function is used to check if a given FID/group exists in AD
    param(
    	[Parameter(Mandatory=$false)]        
    	[string]$domain="nam",               # the domain that FID belongs to
    	[Parameter(Mandatory=$True)]         
    	[string]$checkvalue,                 # the FID or group name
		[Parameter(Mandatory=$False)]
		[ValidateSet("user", "group")]   
    	[string]$checkType="user"            # check type, can only be user,group, default value is user

    )
	If( $checkvalue -match "\\" ){
		$domain=$checkvalue.split('\')[0]
	    $checkvalue = $checkvalue.split('\')[1] # if check value contains domain name, remove it
	}
	Try{
	    return [ADSI]::Exists("WinNT://$domain/$checkvalue,$checkType")
	}Catch{
	    return $false
	}Finally{}
}
Function Popup-Message( $str ){ # popup window for notify user message
    $wshell = New-Object -ComObject Wscript.Shell
	$wshell.Popup($str,0,"Done",0x1) 1>$null
}
Function Get-Inputlist( ){
	param(
		[Parameter(Mandatory=$True)]
		[string]$inputFile,
		[Parameter(Mandatory=$False)]
		[boolean]$inputFileSaved
	)
    If(!(Check-AdminRight)){ Popup-Message "Admin right is required to run this script!";exit}
	# open inputlist.txt(create it if it not exists) and let user to enter the vdi list.
	If(!(test-path $inputFile )){ # $inputFile not exists
		new-item -ItemType File -Path $inputFile -force 1>$null
	}else{
		If( !$inputFileSaved ){ # $inputFile has not been saved
			clear-content $inputFile -force -ErrorAction Ignore
		}else{ # $inputFile has been saved
			$inputlist = (get-content $inputFile |select -Unique|?{$_.trim() -ne ""}|sort)
		}
	}
	while($inputlist -eq $null){
		write-host "Please save vdi(fid) list as below format in opened txt file each line as it's empty, please press 'Ctrl+C' if you want to quit:
		MWDINTVM30544,nam,a000009,uat (for checking port25,HKCU,AA user logfile and any value that relies on FID and domain)
		MWDINTVM30532" ; notepad $inputFile; read-host " "
		# get unique vdi name (exclude empty/blank spaces line)
		$inputlist = (get-content $inputFile |?{$_ -inotmatch 'aaadmin'}|select -Unique|?{$_.trim() -ne ""}|sort)
	}
	return $inputlist
}
Function Export-Result(  ){
	param(
		[Parameter(Mandatory=$True)]
		[object[]]$results,
		[Parameter(Mandatory=$True)]
		[string]$result_type,
		[Parameter(Mandatory=$True)]
		[string]$fileFullname,
		[Parameter(Mandatory=$True)]
		[object]$run_mins,
		[Parameter(Mandatory=$false)]
		[switch]$outfile
	)
	Try{
		If( !$outfile ){
			$results|Export-Csv -Path $fileFullname -UseCulture -NoTypeInformation -Encoding UTF8 -Force -ErrorAction Stop
		}else{
		    $results|out-file -FilePath $fileFullname -Encoding utf8 -Force
		}
		Write-Host -ForegroundColor Yellow "Done export $result_type to $fileFullname in $run_mins s."
	}Catch{
		write-error "Failed to combine csv files" ; $_
	}Finally{}

}
Function New-FileOrFolder($p,[Switch]$d){
  if(!(test-path $p -ErrorAction Ignore)){if($d){new-item -ItemType Directory -path $p -Force 1>$null}else{new-item -ItemType File -path $p -Force 1>$null}}
}
